home *** CD-ROM | disk | FTP | other *** search
/ Revista do CD-ROM 151 / cd-rom 151.iso / internet / firefox / Firefox Setup 3.0 Beta 1.exe / nonlocalized / components / nsUrlClassifierListManager.js < prev    next >
Encoding:
Text File  |  2007-11-09  |  15.9 KB  |  496 lines

  1. //@line 37 "e:\builds\tinderbox\Fx-Rel\WINNT_5.2_Depend\mozilla\toolkit\components\url-classifier\src\nsUrlClassifierListManager.js"
  2.  
  3. const Cc = Components.classes;
  4. const Ci = Components.interfaces;
  5.  
  6. //@line 37 "e:\builds\tinderbox\Fx-Rel\WINNT_5.2_Depend\mozilla\toolkit\components\url-classifier\content\listmanager.js"
  7.  
  8.  
  9. // A class that manages lists, namely white and black lists for
  10. // phishing or malware protection. The ListManager knows how to fetch,
  11. // update, and store lists.
  12. //
  13. // There is a single listmanager for the whole application.
  14. //
  15. // TODO more comprehensive update tests, for example add unittest check 
  16. //      that the listmanagers tables are properly written on updates
  17.  
  18. // How frequently we check for updates (30 minutes)
  19. const kUpdateInterval = 30 * 60 * 1000;
  20.  
  21. function QueryAdapter(callback) {
  22.   this.callback_ = callback;
  23. };
  24.  
  25. QueryAdapter.prototype.handleResponse = function(value) {
  26.   this.callback_.handleEvent(value);
  27. }
  28.  
  29. /**
  30.  * A ListManager keeps track of black and white lists and knows
  31.  * how to update them.
  32.  *
  33.  * @constructor
  34.  */
  35. function PROT_ListManager() {
  36.   this.debugZone = "listmanager";
  37.   G_debugService.enableZone(this.debugZone);
  38.  
  39.   this.currentUpdateChecker_ = null;   // set when we toggle updates
  40.   this.prefs_ = new G_Preferences();
  41.  
  42.   this.updateserverURL_ = null;
  43.  
  44.   this.isTesting_ = false;
  45.  
  46.   this.tablesData = {};
  47.  
  48.   this.observerServiceObserver_ = new G_ObserverServiceObserver(
  49.                                           'xpcom-shutdown',
  50.                                           BindToObject(this.shutdown_, this),
  51.                                           true /*only once*/);
  52.  
  53.   // Lazily create urlCrypto (see tr-fetcher.js)
  54.   this.urlCrypto_ = null;
  55.   
  56.   this.requestBackoff_ = new RequestBackoff(3 /* num errors */,
  57.                                    10*60*1000 /* error time, 10min */,
  58.                                    60*60*1000 /* backoff interval, 60min */,
  59.                                    6*60*60*1000 /* max backoff, 6hr */);
  60.  
  61.   this.dbService_ = Cc["@mozilla.org/url-classifier/dbservice;1"]
  62.                    .getService(Ci.nsIUrlClassifierDBService);
  63. }
  64.  
  65. /**
  66.  * xpcom-shutdown callback
  67.  * Delete all of our data tables which seem to leak otherwise.
  68.  */
  69. PROT_ListManager.prototype.shutdown_ = function() {
  70.   for (var name in this.tablesData) {
  71.     delete this.tablesData[name];
  72.   }
  73. }
  74.  
  75. /**
  76.  * Set the url we check for updates.  If the new url is valid and different,
  77.  * update our table list.
  78.  * 
  79.  * After setting the update url, the caller is responsible for registering
  80.  * tables and then toggling update checking.  All the code for this logic is
  81.  * currently in browser/components/safebrowsing.  Maybe it should be part of
  82.  * the listmanger?
  83.  */
  84. PROT_ListManager.prototype.setUpdateUrl = function(url) {
  85.   G_Debug(this, "Set update url: " + url);
  86.   if (url != this.updateserverURL_) {
  87.     this.updateserverURL_ = url;
  88.     this.requestBackoff_.reset();
  89.     
  90.     // Remove old tables which probably aren't valid for the new provider.
  91.     for (var name in this.tablesData) {
  92.       delete this.tablesData[name];
  93.     }
  94.   }
  95. }
  96.  
  97. /**
  98.  * Set the crypto key url.
  99.  * @param url String
  100.  */
  101. PROT_ListManager.prototype.setKeyUrl = function(url) {
  102.   G_Debug(this, "Set key url: " + url);
  103.   if (!this.urlCrypto_)
  104.     this.urlCrypto_ = new PROT_UrlCrypto();
  105.   
  106.   this.urlCrypto_.manager_.setKeyUrl(url);
  107. }
  108.  
  109. /**
  110.  * Register a new table table
  111.  * @param tableName - the name of the table
  112.  * @param opt_requireMac true if a mac is required on update, false otherwise
  113.  * @returns true if the table could be created; false otherwise
  114.  */
  115. PROT_ListManager.prototype.registerTable = function(tableName, 
  116.                                                     opt_requireMac) {
  117.   this.tablesData[tableName] = {};
  118.   this.tablesData[tableName].needsUpdate = false;
  119.  
  120.   return true;
  121. }
  122.  
  123. /**
  124.  * Enable updates for some tables
  125.  * @param tables - an array of table names that need updating
  126.  */
  127. PROT_ListManager.prototype.enableUpdate = function(tableName) {
  128.   var changed = false;
  129.   var table = this.tablesData[tableName];
  130.   if (table) {
  131.     G_Debug(this, "Enabling table updates for " + tableName);
  132.     table.needsUpdate = true;
  133.     changed = true;
  134.   }
  135.  
  136.   if (changed === true)
  137.     this.maybeToggleUpdateChecking();
  138. }
  139.  
  140. /**
  141.  * Disables updates for some tables
  142.  * @param tables - an array of table names that no longer need updating
  143.  */
  144. PROT_ListManager.prototype.disableUpdate = function(tableName) {
  145.   var changed = false;
  146.   var table = this.tablesData[tableName];
  147.   if (table) {
  148.     G_Debug(this, "Disabling table updates for " + tableName);
  149.     table.needsUpdate = false;
  150.     changed = true;
  151.   }
  152.  
  153.   if (changed === true)
  154.     this.maybeToggleUpdateChecking();
  155. }
  156.  
  157. /**
  158.  * Determine if we have some tables that need updating.
  159.  */
  160. PROT_ListManager.prototype.requireTableUpdates = function() {
  161.   for (var type in this.tablesData) {
  162.     // Tables that need updating even if other tables dont require it
  163.     if (this.tablesData[type].needsUpdate)
  164.       return true;
  165.   }
  166.  
  167.   return false;
  168. }
  169.  
  170. /**
  171.  * Start managing the lists we know about. We don't do this automatically
  172.  * when the listmanager is instantiated because their profile directory
  173.  * (where we store the lists) might not be available.
  174.  */
  175. PROT_ListManager.prototype.maybeStartManagingUpdates = function() {
  176.   if (this.isTesting_)
  177.     return;
  178.  
  179.   // We might have been told about tables already, so see if we should be
  180.   // actually updating.
  181.   this.maybeToggleUpdateChecking();
  182. }
  183.  
  184. PROT_ListManager.prototype.kickoffUpdate_ = function (tableData)
  185. {
  186.   this.startingUpdate_ = false;
  187.   // If the user has never downloaded tables, do the check now.
  188.   // If the user has tables, add a fuzz of a few minutes.
  189.   var initialUpdateDelay = 3000;
  190.   if (tableData != "") {
  191.     // Add a fuzz of 0-5 minutes.
  192.     initialUpdateDelay += Math.floor(Math.random() * (5 * 60 * 1000));
  193.   }
  194.  
  195.   this.currentUpdateChecker_ =
  196.     new G_Alarm(BindToObject(this.checkForUpdates, this),
  197.                 initialUpdateDelay);
  198. }
  199.  
  200. /**
  201.  * Determine if we have any tables that require updating.  Different
  202.  * Wardens may call us with new tables that need to be updated.
  203.  */ 
  204. PROT_ListManager.prototype.maybeToggleUpdateChecking = function() {
  205.   // If we are testing or dont have an application directory yet, we should
  206.   // not start reading tables from disk or schedule remote updates
  207.   if (this.isTesting_)
  208.     return;
  209.  
  210.   // We update tables if we have some tables that want updates.  If there
  211.   // are no tables that want to be updated - we dont need to check anything.
  212.   if (this.requireTableUpdates() === true) {
  213.     G_Debug(this, "Starting managing lists");
  214.     this.startUpdateChecker();
  215.  
  216.     // Multiple warden can ask us to reenable updates at the same time, but we
  217.     // really just need to schedule a single update.
  218.     if (!this.currentUpdateChecker && !this.startingUpdate_) {
  219.       this.startingUpdate_ = true;
  220.       // check the current state of tables in the database
  221.       this.dbService_.getTables(BindToObject(this.kickoffUpdate_, this));
  222.     }
  223.   } else {
  224.     G_Debug(this, "Stopping managing lists (if currently active)");
  225.     this.stopUpdateChecker();                    // Cancel pending updates
  226.   }
  227. }
  228.  
  229. /**
  230.  * Start periodic checks for updates. Idempotent.
  231.  * We want to distribute update checks evenly across the update period (an
  232.  * hour).  To do this, we pick a random number of time between 0 and 30
  233.  * minutes.  The client first checks at 15 + rand, then every 30 minutes after
  234.  * that.
  235.  */
  236. PROT_ListManager.prototype.startUpdateChecker = function() {
  237.   this.stopUpdateChecker();
  238.   
  239.   // Schedule the first check for between 15 and 45 minutes.
  240.   var repeatingUpdateDelay = kUpdateInterval / 2;
  241.   repeatingUpdateDelay += Math.floor(Math.random() * kUpdateInterval);
  242.   this.updateChecker_ = new G_Alarm(BindToObject(this.initialUpdateCheck_,
  243.                                                  this),
  244.                                     repeatingUpdateDelay);
  245. }
  246.  
  247. /**
  248.  * Callback for the first update check.
  249.  * We go ahead and check for table updates, then start a regular timer (once
  250.  * every 30 minutes).
  251.  */
  252. PROT_ListManager.prototype.initialUpdateCheck_ = function() {
  253.   this.checkForUpdates();
  254.   this.updateChecker_ = new G_Alarm(BindToObject(this.checkForUpdates, this), 
  255.                                     kUpdateInterval, true /* repeat */);
  256. }
  257.  
  258. /**
  259.  * Stop checking for updates. Idempotent.
  260.  */
  261. PROT_ListManager.prototype.stopUpdateChecker = function() {
  262.   if (this.updateChecker_) {
  263.     this.updateChecker_.cancel();
  264.     this.updateChecker_ = null;
  265.   }
  266.   // Cancel the oneoff check from maybeToggleUpdateChecking.
  267.   if (this.currentUpdateChecker_) {
  268.     this.currentUpdateChecker_.cancel();
  269.     this.currentUpdateChecker_ = null;
  270.   }
  271. }
  272.  
  273. /**
  274.  * Provides an exception free way to look up the data in a table. We
  275.  * use this because at certain points our tables might not be loaded,
  276.  * and querying them could throw.
  277.  *
  278.  * @param table String Name of the table that we want to consult
  279.  * @param key String Key for table lookup
  280.  * @param callback nsIUrlListManagerCallback (ie., Function) given false or the
  281.  *        value in the table corresponding to key.  If the table name does not
  282.  *        exist, we return false, too.
  283.  */
  284. PROT_ListManager.prototype.safeLookup = function(key, callback) {
  285.   try {
  286.     G_Debug(this, "safeLookup: " + key);
  287.     var cb = new QueryAdapter(callback);
  288.     this.dbService_.lookup(key,
  289.                            BindToObject(cb.handleResponse, cb),
  290.                            true);
  291.   } catch(e) {
  292.     G_Debug(this, "safeLookup masked failure for key " + key + ": " + e);
  293.     callback.handleEvent("");
  294.   }
  295. }
  296.  
  297. /**
  298.  * Updates our internal tables from the update server
  299.  *
  300.  * @returns true when a new request was scheduled, false if an old request
  301.  *          was still pending.
  302.  */
  303. PROT_ListManager.prototype.checkForUpdates = function() {
  304.   // Allow new updates to be scheduled from maybeToggleUpdateChecking()
  305.   this.currentUpdateChecker_ = null;
  306.  
  307.   if (!this.updateserverURL_) {
  308.     G_Debug(this, 'checkForUpdates: no update server url');
  309.     return false;
  310.   }
  311.  
  312.   // See if we've triggered the request backoff logic.
  313.   if (!this.requestBackoff_.canMakeRequest())
  314.     return false;
  315.  
  316.   // Grab the current state of the tables from the database
  317.   this.dbService_.getTables(BindToObject(this.makeUpdateRequest_, this));
  318.   return true;
  319. }
  320.  
  321. /**
  322.  * Method that fires the actual HTTP update request.
  323.  * First we reset any tables that have disappeared.
  324.  * @param tableData List of table data already in the database, in the form
  325.  *        tablename;<chunk ranges>\n
  326.  */
  327. PROT_ListManager.prototype.makeUpdateRequest_ = function(tableData) {
  328.   var tableNames = {};
  329.   for (var tableName in this.tablesData) {
  330.     if (this.tablesData[tableName].needsUpdate)
  331.       tableNames[tableName] = true;
  332.   }
  333.  
  334.   var request = "";
  335.  
  336.   // For each table already in the database, include the chunk data from
  337.   // the database
  338.   var lines = tableData.split("\n");
  339.   for (var i = 0; i < lines.length; i++) {
  340.     var fields = lines[i].split(";");
  341.     if (tableNames[fields[0]]) {
  342.       request += lines[i] + "\n";
  343.       delete tableNames[fields[0]];
  344.     }
  345.   }
  346.  
  347.   // For each requested table that didn't have chunk data in the database,
  348.   // request it fresh
  349.   for (var tableName in tableNames) {
  350.     request += tableName + ";\n";
  351.   }
  352.  
  353.   G_Debug(this, 'checkForUpdates: scheduling request..');
  354.   var streamer = Cc["@mozilla.org/url-classifier/streamupdater;1"]
  355.                  .getService(Ci.nsIUrlClassifierStreamUpdater);
  356.   try {
  357.     streamer.updateUrl = this.updateserverURL_;
  358.   } catch (e) {
  359.     G_Debug(this, 'invalid url');
  360.     return;
  361.   }
  362.  
  363.   if (!streamer.downloadUpdates(request,
  364.                                 BindToObject(this.updateSuccess_, this),
  365.                                 BindToObject(this.updateError_, this),
  366.                                 BindToObject(this.downloadError_, this))) {
  367.     G_Debug(this, "pending update, wait until later");
  368.   }
  369. }
  370.  
  371. /**
  372.  * Callback function if the update request succeeded.
  373.  * @param waitForUpdate String The number of seconds that the client should
  374.  *        wait before requesting again.
  375.  */
  376. PROT_ListManager.prototype.updateSuccess_ = function(waitForUpdate) {
  377.   G_Debug(this, "update success: " + waitForUpdate);
  378.   if (waitForUpdate) {
  379.     var delay = parseInt(waitForUpdate, 10);
  380.     // As long as the delay is something sane (5 minutes or more), update
  381.     // our delay time for requesting updates
  382.     if (delay >= (5 * 60) && this.updateChecker_)
  383.       this.updateChecker_.setDelay(delay * 1000);
  384.   }
  385. }
  386.  
  387. /**
  388.  * Callback function if the update request succeeded.
  389.  * @param result String The error code of the failure
  390.  */
  391. PROT_ListManager.prototype.updateError_ = function(result) {
  392.   G_Debug(this, "update error: " + result);
  393.   // XXX: there was some trouble applying the updates.
  394. }
  395.  
  396. /**
  397.  * Callback function when the download failed
  398.  * @param status String http status or an empty string if connection refused.
  399.  */
  400. PROT_ListManager.prototype.downloadError_ = function(status) {
  401.   G_Debug(this, "download error: " + status);
  402.   // If status is empty, then we assume that we got an NS_CONNECTION_REFUSED
  403.   // error.  In this case, we treat this is a http 500 error.
  404.   if (!status) {
  405.     status = 500;
  406.   }
  407.   status = parseInt(status, 10);
  408.   this.requestBackoff_.noteServerResponse(status);
  409.  
  410.   // Try again in a minute
  411.   this.currentUpdateChecker_ =
  412.     new G_Alarm(BindToObject(this.checkForUpdates, this), 60000);
  413. }
  414.  
  415. PROT_ListManager.prototype.QueryInterface = function(iid) {
  416.   if (iid.equals(Ci.nsISupports) ||
  417.       iid.equals(Ci.nsIUrlListManager) ||
  418.       iid.equals(Ci.nsITimerCallback))
  419.     return this;
  420.  
  421.   Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
  422.   return null;
  423. }
  424. //@line 42 "e:\builds\tinderbox\Fx-Rel\WINNT_5.2_Depend\mozilla\toolkit\components\url-classifier\src\nsUrlClassifierListManager.js"
  425.  
  426. var modScope = this;
  427. function Init() {
  428.   // Pull the library in.
  429.   var jslib = Cc["@mozilla.org/url-classifier/jslib;1"]
  430.               .getService().wrappedJSObject;
  431.   Function.prototype.inherits = jslib.Function.prototype.inherits;
  432.   modScope.G_Preferences = jslib.G_Preferences;
  433.   modScope.G_PreferenceObserver = jslib.G_PreferenceObserver;
  434.   modScope.G_ObserverServiceObserver = jslib.G_ObserverServiceObserver;
  435.   modScope.G_Debug = jslib.G_Debug;
  436.   modScope.G_Assert = jslib.G_Assert;
  437.   modScope.G_debugService = jslib.G_debugService;
  438.   modScope.G_Alarm = jslib.G_Alarm;
  439.   modScope.BindToObject = jslib.BindToObject;
  440.   modScope.PROT_XMLFetcher = jslib.PROT_XMLFetcher;
  441.   modScope.PROT_UrlCrypto = jslib.PROT_UrlCrypto;
  442.   modScope.RequestBackoff = jslib.RequestBackoff;
  443.  
  444.   // We only need to call Init once.
  445.   modScope.Init = function() {};
  446. }
  447.  
  448. // Module object
  449. function UrlClassifierListManagerMod() {
  450.   this.firstTime = true;
  451.   this.cid = Components.ID("{ca168834-cc00-48f9-b83c-fd018e58cae3}");
  452.   this.progid = "@mozilla.org/url-classifier/listmanager;1";
  453. }
  454.  
  455. UrlClassifierListManagerMod.prototype.registerSelf = function(compMgr, fileSpec, loc, type) {
  456.   if (this.firstTime) {
  457.     this.firstTime = false;
  458.     throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
  459.   }
  460.   compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  461.   compMgr.registerFactoryLocation(this.cid,
  462.                                   "UrlClassifier List Manager Module",
  463.                                   this.progid,
  464.                                   fileSpec,
  465.                                   loc,
  466.                                   type);
  467. };
  468.  
  469. UrlClassifierListManagerMod.prototype.getClassObject = function(compMgr, cid, iid) {  
  470.   if (!cid.equals(this.cid))
  471.     throw Components.results.NS_ERROR_NO_INTERFACE;
  472.   if (!iid.equals(Ci.nsIFactory))
  473.     throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  474.  
  475.   return this.factory;
  476. }
  477.  
  478. UrlClassifierListManagerMod.prototype.canUnload = function(compMgr) {
  479.   return true;
  480. }
  481.  
  482. UrlClassifierListManagerMod.prototype.factory = {
  483.   createInstance: function(outer, iid) {
  484.     if (outer != null)
  485.       throw Components.results.NS_ERROR_NO_AGGREGATION;
  486.     Init();
  487.     return (new PROT_ListManager()).QueryInterface(iid);
  488.   }
  489. };
  490.  
  491. var ListManagerModInst = new UrlClassifierListManagerMod();
  492.  
  493. function NSGetModule(compMgr, fileSpec) {
  494.   return ListManagerModInst;
  495. }
  496.